﻿
namespace Patterns4Net.Core.CecilExtensions.CILPatternsMatching
{
    using System;
    using System.Diagnostics.Contracts;
    using Mono.Cecil.Cil;

    public class PredicateInstructionMatcher : InstructionMatcher
    {
        private readonly Func<Instruction, Instruction> nextInstruction;

        private readonly Func<Instruction, bool> instructionMatch;

        public PredicateInstructionMatcher(Func<Instruction, bool> instructionMatch)
        {
            Contract.Requires(instructionMatch != null);
            Contract.Ensures(this.instructionMatch == instructionMatch);
            Contract.Ensures(this.nextInstruction != null);
            this.instructionMatch = instructionMatch;
            this.nextInstruction = x => x.Next;
        }

        public PredicateInstructionMatcher(Func<Instruction, bool> instructionMatch, Func<Instruction, Instruction> nextInstruction)
        {
            Contract.Requires(instructionMatch != null);
            Contract.Requires(nextInstruction != null);
            Contract.Ensures(this.instructionMatch == instructionMatch);
            Contract.Ensures(this.nextInstruction == nextInstruction);
            this.instructionMatch = instructionMatch;
            this.nextInstruction = nextInstruction;
        }

        public override bool Matches(Instruction instruction, out Instruction next)
        {
            if (instruction.OpCode == OpCodes.Br || instruction.OpCode == OpCodes.Br_S)
            {
                Contract.Assume(instruction.Operand is Instruction, "Branch instruction should have other instruction as operand.");
                next = (Instruction)instruction.Operand;
                return true;
            }

            if (instruction.OpCode == OpCodes.Nop)
            {
                next = instruction.Next;
                return true;
            }

            next = this.nextInstruction.Invoke(instruction);
            this.Found = this.instructionMatch.Invoke(instruction);
            return this.Found;
        }
    }
}
